{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Project name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.运行前准备\n",
    "\n",
    "#### 1.1依赖模块\n",
    "```bash\n",
    "pip3 install numpy # 数学库\n",
    "pip3 install matplotlib # 绘图库\n",
    "pip3 install tqdm # 进度条模块\n",
    "pip3 install ipywidgets # 进度条相关依赖\n",
    "pip3 install pillow # 图像处理\n",
    "pip3 install opencv-python # 视觉库\n",
    "```\n",
    "\n",
    "#### 1.2其它\n",
    "\n",
    "以下代码中使用了tqdm模块来绘制进度条，并且需要安装ipywidgets模块来在jupyter中显示。\n",
    "\n",
    "```bash\n",
    "pip3 install ipywidgets\n",
    "```\n",
    "\n",
    "如果有相关报错请安装或更新这两个模块或者注释掉相关代码（对应代码均有注释说明）\n",
    "```python\n",
    "# 绘制进度条\n",
    "p = tqdm(total=idate_count)\n",
    "# 更新进度条\n",
    "p.update(1)\n",
    "# 结束进度条\n",
    "p.close()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.导入相关模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import csv\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from tqdm.notebook import tqdm\n",
    "from PIL import Image, ImageDraw, ImageFont"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.数据处理\n",
    "\n",
    "先用excel将xlsx文件导出为csv文件，方便处理\n",
    "\n",
    "#### 3.1读取文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取csv文件\n",
    "with open(\"国家地铁里程历程.csv\", 'r',encoding='UTF-8-SIG') as f:\n",
    "    csv_reader = list(csv.reader(f))\n",
    "    csv_head = csv_reader[0]\n",
    "    csv_body = csv_reader[1:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.2数据预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# csv_body 每一列最前面的正整数之前全部设为 0\n",
    "\n",
    "country_count = len(csv_head)-1 # 国家数量\n",
    "\n",
    "previous_status = [0 for i in range(country_count)]\n",
    "for i in range(len(csv_body)):\n",
    "    for j in range(country_count):\n",
    "        if previous_status == [1 for i in range(country_count)]:\n",
    "            break\n",
    "\n",
    "        if csv_body[i][j+1] == '' and previous_status[j] == 0:\n",
    "            csv_body[i][j+1] = '0'\n",
    "        else:\n",
    "            previous_status[j] = 1\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成起始数据\n",
    "start_data = csv_body[0][1:]\n",
    "for i in csv_body[0:2]:\n",
    "    for j in range(country_count):\n",
    "        if i[j + 1] != '':\n",
    "            start_data[j] = i[j + 1]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 向列表中插入需要插值的日期\n",
    "\n",
    "csv_body_idate = []\n",
    "for i in csv_body[2:]:\n",
    "    for j in range(1, 13):\n",
    "        if j == 12:\n",
    "            temp = [[i[0] + \".\" + str(j)] + i[1:]]\n",
    "        else:\n",
    "            temp = [[i[0] + \".\" + str(j)] + ['' for i in range(country_count)]]\n",
    "        csv_body_idate += temp\n",
    "\n",
    "csv_body_idate[0] = ['1900.1'] + start_data\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.3对数据进行插值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 对列表进行线性插值\n",
    "\n",
    "idate_count = len(csv_body_idate)\n",
    "\n",
    "# 转置矩阵，方便插值\n",
    "interp_arr = np.transpose(np.array(csv_body_idate))\n",
    "\n",
    "x = [i for i in range(1,idate_count+1)]\n",
    "\n",
    "out_arr = np.array([interp_arr[0]])\n",
    "\n",
    "for i in range(country_count):\n",
    "    xp = []\n",
    "    yp = []\n",
    "    for j in range(idate_count):\n",
    "        if interp_arr[i + 1][j] != '':\n",
    "            xp.append(float(x[j]))\n",
    "            yp.append(float(interp_arr[i + 1][j]))\n",
    "    y = np.interp(x, xp, yp)\n",
    "    out_arr = np.append(out_arr, [y], axis=0)\n",
    "\n",
    "# 数据只保留两位小数\n",
    "out_arr = [out_arr[0].tolist()] + list(map(lambda x:list(map(lambda y:round(float(y),2),x)),out_arr[1:]))\n",
    "\n",
    "# 转置矩阵\n",
    "out_arr = np.transpose(np.array(out_arr))\n",
    "\n",
    "# 输出到列表\n",
    "csv_body_out = out_arr.tolist()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.4输出文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 保存列表\n",
    "\n",
    "with open(\"output.csv\", 'w',encoding='UTF-8-SIG',newline='') as f:\n",
    "   writer = csv.writer(f)\n",
    "   writer.writerow(csv_head)\n",
    "   writer.writerows(csv_body_out)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.绘图\n",
    "\n",
    "#### 4.1定义相关函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义绘制设置函数\n",
    "def draw_setting(**kwargs):\n",
    "\n",
    "    kw={'x':1920,'y':1080,'set_dpi':300}\n",
    "    for k,v in kwargs.items():\n",
    "        if kw.get(k) != None:\n",
    "            kw[k] = v\n",
    "\n",
    "    plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签\n",
    "    plt.rcParams['axes.unicode_minus']=False #用来正常显示负号 #有中文出现的情况，需要u'内容'\n",
    "\n",
    "    # # 去掉图例 legend=False\n",
    "    # # 缩小柱间距 width=0.8\n",
    "    fig, ax = plt.subplots(figsize=(kw['x']/kw['set_dpi'],kw['y']/kw['set_dpi']),dpi=kw['set_dpi'])\n",
    "\n",
    "    # fig = plt.figure(figsize=(1920/set_dpi,1080/set_dpi),dpi=set_dpi)\n",
    "    # ax = plt.gca()\n",
    "\n",
    "    # 图的左边缘位置\n",
    "    fig.subplots_adjust(left=0.1)\n",
    "\n",
    "    # 逆序显示 y 轴\n",
    "    ax.invert_yaxis()\n",
    "\n",
    "    # 去除四周的边框 (spine.set_visible(False))\n",
    "    [spine.set_visible(False) for spine in ax.spines.values()]\n",
    "\n",
    "    # 去除 x 和 y 轴之间无用的刻度 tick\n",
    "    # 去除 x 轴上无用的标签\n",
    "    ax.tick_params(bottom=False, left=False, labelbottom=False)\n",
    "\n",
    "    # 增加 y 轴标签的尺寸\n",
    "    ax.tick_params(axis='y', labelsize='medium')\n",
    "\n",
    "    return fig,ax\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 4.2绘图相关设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 设置dpi\n",
    "set_dpi = 300\n",
    "\n",
    "# 里程列表\n",
    "mileage_list = list(\n",
    "    map(lambda x: list(map(float, x)), [i[1:] for i in csv_body_out]))\n",
    "\n",
    "# 日期列表\n",
    "# date_list = [i[0] for i in csv_body_out]\n",
    "\n",
    "# 国家列表\n",
    "country_list = csv_head[1:]\n",
    "\n",
    "# 国家颜色\n",
    "color_dict = {\n",
    "    '英国': '#FF8000',\n",
    "    '巴西': '#FFFF00',\n",
    "    '法国': '#80FF00',\n",
    "    '德国': '#00FFFF',\n",
    "    '西班牙': '#8000FF',\n",
    "    '俄罗斯': '#FF00FF',\n",
    "    '日本': '#FF7A7A',\n",
    "    '印度': '#0000FF',\n",
    "    '韩国': '#CA7AFF',\n",
    "    '美国': '#0080FF',\n",
    "    '中国': '#FF0000'\n",
    "}\n",
    "\n",
    "# 图片保存路径\n",
    "image_path = './image/'\n",
    "\n",
    "# 自动创建目录\n",
    "if not os.path.exists(image_path):\n",
    "    os.mkdir(image_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 4.3进行绘制\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# 绘制进度条\n",
    "p = tqdm(total=idate_count)\n",
    "\n",
    "# 循环绘制\n",
    "for i in range(idate_count):\n",
    "\n",
    "    plt.clf()\n",
    "\n",
    "    fig, ax = draw_setting(set_dpi=set_dpi)\n",
    "\n",
    "    vmax = max(mileage_list[i])\n",
    "\n",
    "    z = zip(country_list, mileage_list[i])\n",
    "\n",
    "    # 从大到小排列\n",
    "    sort_z = sorted(z, key=lambda x: x[1], reverse=True)\n",
    "\n",
    "    filter_z = []\n",
    "    # 过滤里程为0的\n",
    "    for j in sort_z:\n",
    "        if j[1] != 0:\n",
    "            filter_z.append(j)\n",
    "\n",
    "    # 限制十个国家\n",
    "    filter_z = filter_z[:10] if len(filter_z) > 10 else filter_z\n",
    "\n",
    "    # 绘制\n",
    "    for country, mileage in filter_z:\n",
    "\n",
    "        color = color_dict[country]\n",
    "\n",
    "        ax.barh(country, mileage, color=color)\n",
    "\n",
    "        # 右侧实际的值展示\n",
    "        ax.text(mileage + vmax * 0.02,\n",
    "                country,\n",
    "                f'{mileage:,}',\n",
    "                fontsize='medium',\n",
    "                va='center',\n",
    "                color='C0')\n",
    "\n",
    "    # 保存图片\n",
    "    plt.savefig(image_path + str(i) + '.png', transparent=True, dpi=set_dpi)\n",
    "\n",
    "    # 更新进度条\n",
    "    p.update(1)\n",
    "\n",
    "# 结束进度条\n",
    "p.close()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.图片处理\n",
    "\n",
    "#### 5.1添加背景图片"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "#加载背景图片\n",
    "background_img = Image.open('./background.png')\n",
    "\n",
    "# 绘制进度条\n",
    "p = tqdm(total=idate_count)\n",
    "\n",
    "for i in range(idate_count):\n",
    "\n",
    "    # 加载柱状图\n",
    "    chart_img = Image.open('./image/'+str(i)+'.png')\n",
    "\n",
    "    #新建透明底图，mode使用RGBA，保留Alpha透明度，颜色为透明\n",
    "    #Image.new(mode, size, color=0)，color可以用tuple表示，分别表示RGBA的值\n",
    "    target_img = Image.new('RGBA', background_img.size, (0, 0, 0, 0))\n",
    "\n",
    "    #将背景贴到目标图像\n",
    "    target_img.paste(background_img,(0,0)) \n",
    "    \n",
    "    #将条形图贴到目标图像并保留透明度\n",
    "    target_img.paste(chart_img,(0,0),chart_img)#第一个参数表示需要粘贴的图像，中间的是坐标，最后是一个是mask图片，用于指定透明区域，将底图显示出来。\n",
    "\n",
    "    # target.show()\n",
    "    target_img.save('./image/'+str(i)+'.png')  # 保存图片\n",
    "\n",
    "    # 更新进度条\n",
    "    p.update(1)\n",
    "\n",
    "# 结束进度条\n",
    "p.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 5.2添加文字"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 绘制进度条\n",
    "p = tqdm(total=idate_count)\n",
    "\n",
    "for i in range(idate_count):\n",
    "    target_img = Image.open('./image/'+str(i)+'.png')\n",
    "\n",
    "    draw = ImageDraw.Draw(target_img)\n",
    "\n",
    "    # 绘制标题\n",
    "    title_fontStyle = ImageFont.truetype(\"simhei.ttf\", 80, encoding=\"utf-8\")\n",
    "    draw.text((620, 30), \"全球各国地铁里程\", \"#262626\", font=title_fontStyle)\n",
    "\n",
    "    # 绘制年份\n",
    "    year = str(1900+int(i/60)*5) # 每5年更新一次\n",
    "    year_fontStyle = ImageFont.truetype(\"simhei.ttf\", 100, encoding=\"utf-8\")\n",
    "    draw.text((1460, 800), year, \"#bfbfbf\", font=year_fontStyle)\n",
    "\n",
    "    # 绘制班级姓名水印\n",
    "    # 字体的格式 这里的simhei.ttf需要有这个字体\n",
    "    mark_fontStyle = ImageFont.truetype(\"simhei.ttf\", 20, encoding=\"utf-8\")\n",
    "    draw.text((5, 5), \"20软件6班张某人\", \"#737373\", font=mark_fontStyle)\n",
    "\n",
    "    target_img.save('./image/'+str(i)+'.png')  # 保存图片\n",
    "\n",
    "    # 更新进度条\n",
    "    p.update(1)\n",
    "\n",
    "# 结束进度条\n",
    "p.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.视频合成\n",
    "\n",
    "以下使用opencv模块来合成视频，opencv合成出来的视频质量可能不高\n",
    "\n",
    "如需合成更清晰的视频并加上背景音乐可以用ffmpeg，或者直接用视频处理软件来处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "\n",
    "# 绘制进度条\n",
    "p = tqdm(total=idate_count)\n",
    "\n",
    "# DIVX是实现 MPEG-4 标准的编码器\n",
    "video=cv2.VideoWriter('./output.mp4',cv2.VideoWriter_fourcc(*'DIVX'),12,(1920,1080))  #定义保存视频目录名称及压缩格式，fps=10,像素为1920*1080\n",
    "for i in range(idate_count):\n",
    "    img=cv2.imread('./image/'+str(i)+'.png',1)  #读取图片\n",
    "    # img=cv2.resize(img,(1920,1080)) #将图片转换为1920*1080\n",
    "    video.write(img)   #写入视频\n",
    "    # 更新进度条\n",
    "    p.update(1)\n",
    "\n",
    "video.release()\n",
    "\n",
    "# 结束进度条\n",
    "p.close()\n"
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "eb4cdb330c5ea7232880705c0e79ad22649a7c708042624124f8ff95c4dc218f"
  },
  "kernelspec": {
   "display_name": "Python 3.7.9 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.9"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
